<script type="typescript">
import { debounce } from 'lodash';
import { request } from '../../../../stores/auth';
import Spinner from '../../../../components/Spinner.svelte';
import Icon from '../../../../components/Icon.svelte';
import { createEventDispatcher } from 'svelte';
import type { UserSearch, UserSearchUser } from '../../../../types/user';
const dispatch = createEventDispatcher();
* A list of UUIDs to hide from the search, so we can skip showing users that
* are already members.
*/
export let hideUuids: string[] = [];
* Contains whether the search results are currently loading so a spinner
* can be shown.
*/
let loading = false;
* Binding to the search terms the user has entered.
*/
let search = '';
* A list of search results from the backend
*/
let searchResults: UserSearchUser[] = [];
$: performSearch(search);
const onInput = debounce((event) => {
search = event.target.value;
}, 250);
* Call the backend and fetch user results for the user's given search terms.
*
* @param search terms to search for
*/
async function performSearch(search: string) {
if (search === '') {
return;
}
loading = true;
try {
let result = await request<UserSearch>(`/web/v1/users/search?q=${search}`);
searchResults = result.users || [];
} catch (e: unknown) {
console.log(e);
} finally {
loading = false;
}
}
* Send an event back to the parent component whenever a user is selected.
*
* @param member member to send to the parent component
*/
function dispatchNewMember(member: UserSearchUser) {
dispatch('new', member);
searchResults = [];
search = '';
}
</script>
<div class="flex items-center">
<input
placeholder="Start typing a username..."
on:input={(e) => {
searchResults = [];
onInput(e);
}}
type="text"
class="border border-gray-200 dark:border-gray-700 bg-transparent text-sm rounded-lg block p-2.5 ring-blue-500 w-[100%] lg:w-[25%] focus:border-blue-500 mr-1"
/>
{#if loading}
<div class="relative h-10 w-10">
<Spinner />
</div>
{/if}
</div>
{#if searchResults.length !== 0}
<div class="mt-4 grid md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
{#each searchResults as result}
{#if !hideUuids.includes(result.user_uuid)}
<button on:click={() => dispatchNewMember(result)} class="flex items-center">
{#if result.picture_url}
<img
alt={result.display_name}
src={result.picture_url}
class="rounded-[50%] h-[4rem] w-[4rem] mr-4"
/>
{:else}
<div
class="h-[4rem] w-[4rem] rounded-[50%] text-gray-300 bg-gray-100 dark:bg-gray-800 overflow-hidden mr-4"
>
<Icon height="4rem" width="4rem" name="user" />
</div>
{/if}
<span class="card-header">{result.display_name}</span>
</button>
{/if}
{/each}
</div>
{/if}